import { EzGameReqInstance } from "./EzGameReqInstance";
import { EzGameNetManager } from "./EzGameNetManager";
import * as ezgame_pb from "../pb/ezgame_pb";
import { create } from "@bufbuild/protobuf";
import { EzGameUtils } from "./EzGameUtils";
import { ApiMetaData } from "./ApiMetaData";

export class EzGameNet{
    public url?:string
    public log:boolean = false
    public name:string = "default";
    public timeOut:number = 3000; //默认超时时间为3秒
    public maxReconnectAttempts: number = 5; //最大重连次数
    public reconnectInterval: number = 3000; // 重连间隔(ms)
    private serialNum = 0;


    private ws:WebSocket|null = null;

    private reconnectTimer: any = null;//重连定时器
    private reconnectCount: number = 0;//重连次数
    private isManualClose: boolean = false;//是否是手动关闭
    public status:number =0;//0初始化状态 1连接中 2已连接 3已断开


    // 网络事件回调系统
    public onNetOpen?: (( ev: Event) => any)|null;
    public onNetClose?: ((e: any) => void)|null
    public onNetError?: ((error: Event) => void)|null;

    public onReconnecting?: (attempt: number) => void;
    public onReconnectFailed?: () => void;

    public beforeSend?: (msg: any) => any | void;
    public afterSend?: (msg: any) => void;

    public received?: (data: EzGameReqInstance<any,any>) => any | void;
    public afterReceive?: (data: any) => void;

    private msgs:Map<number,EzGameReqInstance<any,any>> = new Map()

    //关闭
    close(){
        if(this.ws) {
            this.ws.close();
            this.ws = null;
            if(this.log) {
                console.log("WebSocket连接已关闭,name:", this.name);
            }
        } else {
            if(this.log) {
                console.warn("没有可关闭的WebSocket连接");
            }
        }
    }
    //重连
    reconnect(){

    }
    //连接
    connect(){
        if(!this.url) {
            throw new Error("请先设置url");
        }
        this.isManualClose = false;
        this.clearReconnectTimer()
        this.status=1;
        this.ws = new WebSocket(this.url);
        this.ws.binaryType = 'arraybuffer';
        this.ws.onopen = this.onWsOpen.bind(this)
        this.ws.onmessage = this.onWsMessage.bind(this)
        
        this.ws.onerror = this.onWsError.bind(this)
        this.ws.onclose = this.onWsClose.bind(this)
    }
    private onWsOpen(e:any){
        this.reconnectCount = 0; // 重置重连次数
        this.status = 2;
        if(this.log) {
            console.log(`[${this.name}] Connected`);
        }
        this.register()
        this.onNetOpen?.(e);
    }
    private onWsMessage(e:MessageEvent){
        let byteData = e.data as ArrayBuffer


        let data = EzGameUtils.objDataDecode(ezgame_pb.ClientRespMsgSchema,new Uint8Array(byteData.slice(0))) as ezgame_pb.ClientRespMsg
        let cmd = data.cmd
        let code = data.code
        //推送消息（心跳等）
        if(this.isPushMsg(data)){
            return
        }
        if(this.isBusinessMsg(data)){
            return
        }
        //TODO 应该发出一个事件，来处理已经超时后才响应得请求或者未注册得推送消息
        //检查是否是推送消息的id
        console.error("收到消息，cmd:"+cmd+" msgSerial:"+data.msgTag+"  但请求实例不存在！无法处理")
    }
    private onWsError(e:any){
        console.error(`连接异常[${this.name}]:`,e);
        this.onNetError?.(e)
    }
    private onWsClose(e:any){
        console.error(`连接关闭[${this.name}]:`,e);
        this.status = 3;
        this.onNetClose?.(e)
    }
    isBusinessMsg(data: ezgame_pb.ClientRespMsg) {
        let tag = data.msgTag
        let cmd = data.cmd
        let reqInstance = this.msgs.get(tag)
        if(!reqInstance){//已经超时移除了
            return false
        }
        reqInstance.status = EzGameReqInstance.STATUS_RETURNED
        clearTimeout(reqInstance.timeOutId)
        let end = performance.now()
        reqInstance.respTime = end
        let cost = end - reqInstance.sendTime
        //检测返回码
        let code = data.code
        if(code === 0){//0表示成功
            //解析返回值
            if(reqInstance.respDataDecoder){
                try{
                    let respData = reqInstance.respDataDecoder(data.data)
                    reqInstance.respData = respData
                }catch(e){
                    reqInstance.respCode = -5
                    let error = e as Error
                    reqInstance.respErrorMsg = "返回数据解析异常!cmd:"+reqInstance.cmd+" msgSerial:"+reqInstance.msgSerial+" cost:"+cost+"ms  error:"+error.message    
                    console.error(error.stack)
                    if(!reqInstance.async){
                        reqInstance.reject?.(reqInstance)
                    }
                    return
                }
            }
            if (this.log){
                let second = cmd&0xffff
                let first = (cmd>>16)&0xffff
                console.log(`[${this.name}] Received: cmd:`+cmd+"("+first+"_"+second+")"+" msgSerial:"+tag+" cost:"+cost+"ms  data:", reqInstance.respData);
            }
            // this.dispatchMessage(reqInstance);
            reqInstance.resolve?.(reqInstance)
        }else{
            reqInstance.respCode = code
            //从元数据中拿错误信息
            let msg = ApiMetaData.getErrorCodeMeta(code)
            reqInstance.respErrorMsg = msg?msg:"未知错误 code:"+code
            //TODO 先执行全局失败得方法，再执行我们自己得
            reqInstance.resolve?.(reqInstance)
        }
        return true
    }
    isPushMsg(data: ezgame_pb.ClientRespMsg) {
        let cmd = data.cmd
        //心跳消息
        if(cmd===1){//心跳消息
            return true
        }
        //TODO 其他推送消息
        let meta = EzGameNetManager.getServerPushMeta(this.name,cmd)
        if(meta){
            try{
                let pushMsg = meta.decoder(data.data)
                meta.handler(pushMsg)
            }catch(e){
                console.error("推送消息处理异常!cmd:"+cmd+" error:"+(e as Error).message)
            }
            return true
        }
        return false
    }
    private dispatchMessage(data: EzGameReqInstance<any,any>): void {
        
    }

    private clearReconnectTimer(): void {
        if (this.reconnectTimer) {
            clearTimeout(this.reconnectTimer);
            this.reconnectTimer = null;
        }
    }


    public static create(url?:string,options?:NetOptions ):EzGameNet {
        let targetOptions :NetOptions = {
            log:false,
        }
        if(options){
            Object.assign(targetOptions,options)
        }
        let ins = new EzGameNet();
        ins.url = url;
        ins.log = targetOptions.log as boolean;
        if(targetOptions.onNetOpen){
            ins.onNetOpen = targetOptions.onNetOpen
        }
        if(targetOptions.onNetError){
            ins.onNetError = targetOptions.onNetError
        }
        if(targetOptions.onNetClose){
            ins.onNetClose = targetOptions.onNetClose
        } 
        return ins;
    }
    // public static create(url?:string, log:boolean = false,name:string="default"):EzGameNet {
    //     let ins = new EzGameNet();
    //     ins.url = url;
    //     ins.log = log;
    //     return ins;
    // }

    //发送同步等待返回
    public async sendMsg(reqInstance:EzGameReqInstance<any,any>) {
        if(!this.ws) {
            reqInstance.status = EzGameReqInstance.STATUS_SENDFAIL
            reqInstance.respCode = -1
            reqInstance.respErrorMsg = "WebSocket还未实例化，发送消息失败"
            //TODO 先执行全局异常
            if(!reqInstance.async){
                reqInstance.reject?.(reqInstance)
            }
            return
        }
        if(this.ws.readyState !== WebSocket.OPEN) {
            reqInstance.status = EzGameReqInstance.STATUS_SENDFAIL
            reqInstance.respCode = -2
            reqInstance.respErrorMsg = "WebSocket未连接，请先调用connect方法"
            if(!reqInstance.async){
                reqInstance.reject?.(reqInstance)
            }
            return
        }
        if(this.log) {
            console.log("发送消息:", reqInstance);
        }

        //外层的消息
        let sendMsg = create(ezgame_pb.ClientReqMsgSchema)
        sendMsg.cmd = reqInstance.cmd
        //TODO 模式和ver要全局设置
        sendMsg.moshu = 1
        sendMsg.ver = 1
        sendMsg.msgSerial = this.getNextSerial()
        reqInstance.msgSerial = sendMsg.msgSerial

        if(reqInstance.reqDataEncoder){//有参数编码器，表示有参数
            try{
                sendMsg.data = reqInstance.reqDataEncoder(reqInstance.reqData)
            }catch(e){
                reqInstance.status = EzGameReqInstance.STATUS_SENDFAIL
                reqInstance.respCode = -3
                let error = (e as Error)
                reqInstance.respErrorMsg =error.message
                console.error(error.stack)
                if(!reqInstance.async){
                    reqInstance.reject?.(reqInstance)
                }
                return
            }
        }


        if(reqInstance.respDataDecoder){
            sendMsg.needResp = true;
            this.msgs.set(sendMsg.msgSerial,reqInstance)
            reqInstance.status = EzGameReqInstance.STATUS_WAITRETURN
            reqInstance.timeOutId = setTimeout(()=>{
                reqInstance.status = EzGameReqInstance.STATUS_RETURNTIMEOUT
                this.msgs.delete(sendMsg.msgSerial)
                reqInstance.respCode = -4
                reqInstance.respErrorMsg = "请求cmd:"+sendMsg.cmd+" 超时！"
                if(!reqInstance.async){
                    reqInstance.reject?.(reqInstance)
                }
            },this.timeOut)
        }else{
            reqInstance.status = EzGameReqInstance.STATUS_SENDDONE
        }
        let sendData = EzGameUtils.objDataEncode(ezgame_pb.ClientReqMsgSchema,sendMsg)
        this.ws.send(sendData);

        reqInstance.sendTime = performance.now()
    }
    getNextSerial(): number {
        this.serialNum++;
        return this.serialNum;
    }

    public register(){
        EzGameNetManager.register(this);
    }
    public unRegister(){
        EzGameNetManager.unRegister(this);
    }
}

export interface NetOptions{
    log?:boolean,
    onNetOpen?:(e:any)=>void,
    onNetError?:(e:any)=>void,
    onNetClose?:(e:any)=>void,
}